Skip to content

Fix/stability - #5

Merged
Langegen merged 6 commits into
mainfrom
fix/stability
Nov 6, 2026
Merged

Langegen merged 6 commits into
mainfrom
fix/stability

Conversation

@Langegen

@Langegen Langegen commented Nov 6, 2026

Copy link
Copy Markdown
Owner

No description provided.

Langegen and others added 6 commits November 6, 2026 09:01
Engine:
- A uTP dial that never connected leaked its rx/tx/bitfield (~516 KB each):
  sess_close now frees the session in the connecting branch too.
- The DHT thread no longer calls dht_search under s_bg_mtx: jech answers
  from local storage synchronously through bg_on_event, which takes the same
  (non-recursive) lock -- a node announcing our hash wedged the thread, and
  the netloop and torrentfs_close behind it.
- dht_find_peers waits for in-flight callbacks before its caller frees ctx.
- Blocks from incoming peers are tagged -1 (was peer_gen[-1], which blamed
  and could ban pool slot 2047).
- The SD cache reads only DONE pieces (a slot exists before its write ends).
- select_file during shutdown no longer frees aq buffers under the netloop.
- meta_pipe_run closes only threads it created.
- sess_close reads the MSE flags before peer_nb_free zeroes them (the
  plaintext-retry branch was dead); rotate_out counts choked peers again.
- Each live engine gets its own cache file name (slot): the next episode's
  engine opens before the previous one closes, and both used cache.bin.

App:
- Updates download to .part and are checked (NRO0 magic, header size,
  release size, fclose) before becoming .new; a broken .new is dropped.
- Home's Continue row parks the focus on the screen itself when neither the
  hero nor a poster row exists, and only rebuilds while on top.
- Stremio catalogs landing under a detail screen wait until the tab is on
  top (ui::isOnTop) instead of freeing the card on the focus stack.
- The library +/- answer touches its view only while it is still focused.
- g_libIds is published on the UI thread.
- borealis: exceptions in brls::async tasks are caught (VENDORED.md #9).

Not compiled for the Switch yet (PC build and pctest only).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Magnet metadata: each peer's fetch now ends on the pipeline's stop flag
  (deadline, cancel, another worker's success) and after 30 s; a peer
  trickling keep-alives held a worker, and the open, forever. Test 8 in
  test_suite covers it (it hangs without the fix).
- The player's engine open runs on the worker pool and is cancellable:
  Back stops a magnet's fetch. A small hand-off makes exactly one of the
  task, its callback and the destructor close an engine opened after Back
  (also on app exit, where no brls::sync runs). TorrServer resolves and the
  update download moved off brls::async too; so did the Local tab's magnet
  name resolves, which torrent_meta_abort_all ends on exit.
- App exit: exitEvent aborts the network and magnet fetches before borealis
  joins its task thread; exitDoneEvent writes the library (library::flush),
  whose last save was queued and dropped.
- The end of a file (or next/previous episode) with the settings panel open
  pops the panel too (popPlayerThen finds the player in the stack): it
  closed only the panel, or pushed a second player over the first.
- The "video playing" flag is counted per live player: the next episode's
  player is built before the previous one's destructor turned it off.
- A torrent:// stream has a cancel_fn of its own (torrentfs_read_cancel); an
  nxhttps:// open can be abandoned (httpsrc_open_abortable) so the destructor
  does not wait it out on the UI thread; stream_http_err resets per open.
- SD cache: a DONE piece that cannot be read back is fetched again instead of
  freezing the picture.
- The listener's socket is closed by its own thread only.
- A block for the blocked reader wakes it under the lock (no lost wakeup).
- A failed open keeps its reason on screen and leaves on one Back.

Not compiled for the Switch yet (PC build and pctest only).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- fsutil.hpp (header-only): writeAtomic writes through .tmp with every
  write checked and keeps the previous version as .bak; readFirst falls back
  to the .tmp of a save cut between its renames, then to the .bak, when a
  file is missing or cut short (looksComplete). Used by config.json,
  library.json, history.json, the Stremio key and e-mail, the voice choices
  and magnet.txt -- several were rewritten in place, and a power cut left
  them empty. test_library checks the fallbacks.
- config.json values are JSON-escaped (a quote in a value broke the file);
  the reader understands what is escaped and reads old files unchanged.
- http: 15 s connect timeout and NOSIGNAL on every request (curl's default
  connect timeout is 300 s), answers held in memory capped at 32 MB.
- A .torrent is downloaded to .part and renamed when whole, so a failed
  refetch no longer deletes the file history/Continue resume from; a Jackett
  release played before uses its cached .torrent (works with Jackett down).
- TMDB and the Stremio addons treat non-2xx answers as failures: a proxy's
  502 page was cached over the good copy for hours, an addon's 500 cached
  as "no episodes". Genres from a failed manifest are no longer cached.
- TMDB's cache write is checked and leaves no .tmp behind.
- Subtitles download to .part, and two requests for one file share it.
- UDP tracker transaction ids come from a local generator: srand(time) on
  every announce made them predictable and reset the DHT's rand().
- A reused peer pool slot clears its uTP-retry and plaintext-only flags.

Not compiled for the Switch yet (PC build and pctest only).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Player:
- A direct stream that dies mid-file (it reaches mpv as the end of the file)
  reconnects where it stopped, up to three times, instead of closing the
  player and marking the title watched; after that it says so and B leaves.
  Checked on the PC by killing and restarting the test server mid-play.
- A file of the pack that is not a nameable episode plays as a plain file:
  it kept the previous episode's identity and wrote its progress there.
- The episode list no longer shows the playing episode's progress on
  unplayed files.
- A seek (back to 0:00 too) is the position saved on leaving.
- OK three times unlocks only in a row.

Engine:
- DHT targets are keyed by (hash, owner): the next episode's engine and the
  previous one share a hash for a moment, and the old one's removal took the
  new one's DHT discovery with it. Peers go to every owner of a hash; each
  hash is searched once.
- MSE: the VC resync expects the first 8 keystream bytes at any padB offset
  (it only worked for padB == 0). MSE stays off; the note asks for a new A/B.
- UPnP maps the port with a one-hour lease renewed while running (permanent
  only where the router insists) and deletes the mapping on the way out.

Parsing:
- "S01E05 - 720p" and "S02 - 480p" are no longer episode/season ranges up to
  720/480 (a letter after the number, an end below the start, or past 99/999
  is not a range end). test_release covers it.
- json: escape() escapes control characters, a lone surrogate or \u0000
  reads as U+FFFD, integer() saturates. test_tmdb covers it.

Not compiled for the Switch yet (PC build and pctest only).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Peer receive buffers start at 64 KB and double up to 512 KB only for a
  peer that fills them: every session took 512 KB, ~20 MB at 40 sessions.
- Card artwork is decoded on a worker and only uploaded on the UI thread
  (ui::loadImageAsync); a screen of cards decoded dozens of JPEGs, a w1280
  backdrop is 3.6 MB of pixels, in one frame.
- borealis keeps at most 48 textures no view uses (was 200 -- hundreds of MB
  of backdrops after a browse); textures in use are never dropped.
- The Continue row has its own liveness token: artwork landing for a card
  freed by a rebuild no longer reaches it.
- http::get/postJson reuse one curl handle per thread, keeping connections
  and TLS sessions: the posters of a screen share a connection instead of a
  handshake each. http::shutdown() closes them before main() returns.
- borealis' task thread waits for work instead of sleeping 500 ms between
  batches (VENDORED.md #9): every brls::async task started up to half a
  second late.
- tmdb::clearCache walks the SD card on a worker; "Test connection" asks
  once the clearing is done.
- mpv's verbose log is requested only with the log file on.
- The magnet file-list cache is read once per change, not once per row.

Not compiled for the Switch yet (PC build and pctest only).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- WebP posters are checked before decoding: a file declaring 16k x 16k
  pixels was a 1 GB allocation (posters are at most 4096 a side).
- HTTP tracker replies are capped at 512 KB (was 4 MB): an announce reply is
  a few KB, and the cap bounds the bencode tree an unverified tracker can
  make us build.
- The preferred subtitle language is read on the UI thread, not on the
  worker sorting the results.
- CLAUDE.md: the rules this work turned up (two players alive at once,
  popPlayerThen, screens under another activity, long work off brls::async,
  fsutil saves, HTTP status before caching, kept curl handles).

Not compiled for the Switch yet (PC build and pctest only).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Langegen
Langegen merged commit 2bcc010 into main Nov 6, 2026
1 check failed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant